﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using Foundation.ExtensionMethods;
using Foundation.MarkupExtensions;
using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Highlighting;

namespace Poet.Modules
{
    public class CompletionManager
    {
        public class CompletionParametrs
        {        
            public bool IsTotalUpdate;
            public string SyntaxName;
            public object Sender; 
            public string Text;
            public Regex WholeWordRegex;
            public int Offset;
        }

        private readonly List<string> _allKeywords = new List<string>(); 
        private readonly string _fileName; 
        private readonly int _dictionarySize;
        private readonly EventWaitHandle _checkEvent = new ManualResetEvent(true);
        public readonly Dictionary<string, Word> Dictionary = new Dictionary<string, Word>();
        private readonly List<CompletionParametrs> _checkStack = new List<CompletionParametrs>();
        private List<Match> _matches; 

        public CompletionManager(string fileName, List<IHighlightingDefinition> syntaxes)
        {
            syntaxes.ForEach(s => _allKeywords.AddRange(s.Definition.Keywords));
            _fileName = fileName;
            LoadDictionary(fileName);
            _dictionarySize = Dictionary.Count;
            new Thread(UpdateCompletionList) {IsBackground = true, Priority = ThreadPriority.Lowest}.Start();
        }
  
        public event Action<object, List<ICompletionData>> CompletionListUpdated = (sender, list) => { };

        public void UpdateCompletionList(object sender, string text, int offset, bool updateDictionary, Regex wholeWordRegex, string syntaxName = null)
        {
            var parameters = new CompletionParametrs
                {
                    WholeWordRegex = wholeWordRegex,
                    IsTotalUpdate = updateDictionary,
                    SyntaxName = syntaxName,
                    Sender = sender,
                    Offset = offset,
                    Text = text,
                };

            lock (_checkStack)
            {
                parameters.IsTotalUpdate = parameters.IsTotalUpdate || _checkStack.Any(p => p.Sender == sender && p.IsTotalUpdate);
                _checkStack.Where(p => p.Sender == sender).ToList().ForEach(p => _checkStack.Remove(p));
                _checkStack.Add(parameters);
            }

            _checkEvent.Set();
        }

        private void UpdateCompletionList()
        {
            do
            {
                if (_checkStack.Count == 0) _checkEvent.Reset();
                _checkEvent.WaitOne();

                var parameters = _checkStack.LastOrDefault();
                if (parameters == null) continue;
                lock (_checkStack) _checkStack.Remove(parameters);
                var text = parameters.Text;
                var offset = parameters.Offset;
                var sender = parameters.Sender;
                var syntaxName = parameters.SyntaxName;

                if (parameters.IsTotalUpdate || _matches == null)
                    _matches = parameters.WholeWordRegex.Matches(text).Cast<Match>().ToList();

                foreach (var pair in Dictionary)
                {
                    pair.Value.LocalUsings = 0;
                    pair.Value.GlobalUsings = _allKeywords.Contains(pair.Key) ? 1 : 0;
                    pair.Value.Distance = int.MaxValue;
                }

                foreach (var match in _matches)
                {
                    var key = match.Value;
                    if (!Dictionary.ContainsKey(key))
                        Dictionary.Add(key, new Word(match) {Distance = int.MaxValue});
                    var word = Dictionary[key];
                    var distance = Math.Abs(offset - match.Index);
                    word.LocalUsings++;
                    if (word.LocalUsings > 0)
                        word.Distance = word.Distance > distance ? distance : word.Distance;        
                }

                SetKeywordsDefaults(syntaxName);
                var completionList = CreateCompletionList(Dictionary);
                CompletionListUpdated(sender, completionList);
                Thread.Sleep(70);
            } while (_checkEvent != null);
        }

        private static List<ICompletionData> CreateCompletionList(Dictionary<string, Word> dictionary)
        {
            var words = dictionary.Keys.ToList();
            var wordsCount = dictionary.Count;
            words.Sort((x, y) => Compare(dictionary[y], dictionary[x], wordsCount));
            var completionList =
                words.Select(x => new MyCompletionData(x) { Description = GetDescription(dictionary[x]) })
                     .Cast<ICompletionData>().ToList();
            return completionList;
        }

        private static int Compare(Word x, Word y, int count)
        {
            if (count == 0) return 0;
            var xUsings = x.LocalUsings;
            var yUsings = y.LocalUsings;
            if (xUsings == yUsings)
            {
                xUsings = x.GlobalUsings;
                yUsings = y.GlobalUsings;
            }

            if (xUsings == yUsings && y.Distance != x.Distance)
            {
                return y.Distance - x.Distance;
            }

            var xDistance = x.Distance == 0 ? 1 : x.Distance;
            var yDistance = y.Distance == 0 ? 1 : y.Distance;

            var result = (int) (xUsings/(double) xDistance*count - yUsings/(double) yDistance*count);
            return result == 0 ? string.CompareOrdinal(y.Value, x.Value) : result;
        }

        private static string GetDescription(Word word)
        {
            var description= Localizing.ProvideValue("Usings", "{0}: ") + word.LocalUsings;
             //if (word.Distance < int.MaxValue)
                 //description += Environment.NewLine + Localizing.ProvideValue("Distance: ") + word.Distance;
            return description;
        }

        private void SetKeywordsDefaults(string syntaxName)
        {
            var syntax = HighlightingManager.Instance.HighlightingDefinitions.FirstOrDefault(s => s.Name == syntaxName);
            var keywords = syntax == null ? null : syntax.Definition.Keywords;

            foreach (var keyword in _allKeywords)
            {
                var contained = Dictionary.ContainsKey(keyword);
                var isKeyword = keywords != null && keywords.Contains(keyword);
                var distance = isKeyword ? int.MaxValue/4 : int.MaxValue/2;
                var word = contained
                               ? Dictionary[keyword]
                               : new Word(keyword)
                                   {
                                       GlobalUsings = 1,
                                       Distance = distance,
                                       IsKeyword = isKeyword,
                                   };

                if (word.LocalUsings == 0)
                    word.Distance = distance;

                if (!contained)
                {
                    Dictionary.Add(keyword, word);
                }
            }
        }

        public void SaveDictionary(string fileName = null)
        {
            fileName = fileName ?? _fileName;
            if (string.IsNullOrWhiteSpace(fileName)) return;
            lock (Dictionary)
            {
                if (_dictionarySize == Dictionary.Count) return;
                Dictionary.ForEach(pair => pair.Value.GlobalUsings += pair.Value.LocalUsings);
                var lines = Dictionary.Select(pair => pair.Key + " " + pair.Value.GlobalUsings).ToList();
                File.WriteAllLines(fileName, lines);
            }
        }

        private void LoadDictionary(string fileName = null)
        {
            if (string.IsNullOrWhiteSpace(fileName)) return;
            if (File.Exists(fileName))
            {
                var lines = File.ReadAllLines(fileName);
                foreach (var parts in lines.Select(line => line.Split()))
                {
                    int usings;
                    if(parts.Length < 2 || !int.TryParse(parts[1], out usings)) continue;
                    var word = new Word(parts[0]) {GlobalUsings = usings};
                    Dictionary.Add(word.Value, word);
                }
            }
            else File.Create(fileName).Close();
        }

        public class Word
        {
            public string Value { get; private set; }
            public Match Match { get; private set; }
            public int GlobalUsings { get; set; }
            public int LocalUsings { get; set; }
            public int Distance { get; set; }
            public bool IsKeyword { get; set; }

            public Word(string word)
            {
                Value = word;
            }

            public Word(Match match)
            {
                Match = match;
                Value = match.Value;
            }
        }
    }
}